package com.adtec.yxdemo.utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;

/**
 * RSA 加解密工具类
 */
public class FileEncUtil {
    //加解密算法关键字
    public static final String KEY_ALGORITHM = "RSA";
    // 公钥关键字
    private static final String PUBLIC_KEY = "RSAPublicKey";
    //私钥关键字
    private static final String PRIVATE_KEY = "RSAPrivateKey";
    //公钥私钥保存路径
    private static final String KEY_PATH = "D:\\001\\003";
    //公钥保存文件名
    private static final String PUBLIC_KEY_FILE_NAME = "public.store";
    //私钥保存文件名
    private static final String PRIVATE_KEY_FILE_NAME = "private.store";
    //加密文件后缀名
    private static final String PRIVATE_FILE_SUFFIX = ".asc";
    //默认编码
    public static final String CHARSET = "UTF-8";
    //默认缓冲区大小
    public static final Integer DEFAUT_BUFFER_SIZE = 10 * 1024 * 1024;
    // 最大加密字节数超出最大字节数需要分组加密
    private static int MAX_ENCRYPT_BLOCK = 117;
    //最大解密字节数超出最大字节数需要分组解密
    private static int MAX_DECRYPT_BLOCK = 128;

    /**文件加密
     * @param tarFileName 待加密文件绝对路径
     * @return 加密文件
     * @throws Exception
     */
    public static File encryptFile(String tarFileName) throws Exception {
        //获取公钥
        String publicKey = readFile(KEY_PATH + File.separator + PUBLIC_KEY_FILE_NAME );
        File file = new File(tarFileName);
        FileInputStream fileInputStream = new FileInputStream(file);
        BufferedInputStream in = new BufferedInputStream( fileInputStream , DEFAUT_BUFFER_SIZE );
        byte[] buffer = new byte[(int)file.length()];
        in.read(buffer);
        in.close();
        String base64Code = Base64.encodeBase64String(buffer);
        File encryptFile = new File( tarFileName + PRIVATE_FILE_SUFFIX);
        if(encryptFile.exists()) {
            encryptFile.delete();
        }
        BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(encryptFile));
        BufferedWriter out = new BufferedWriter( new OutputStreamWriter( bos ,CHARSET), DEFAUT_BUFFER_SIZE );
        try {
            String encryptMsg = encrypt( base64Code , publicKey);
            out.write( encryptMsg );
            out.flush();
            out.close();
        } catch (Exception e) {
            throw new Exception("文件加密失败", e);
        }
        return encryptFile;
    }

    /**文件解密
     * @param encryptFilePath 加密(待解密)文件绝对路径
     * @return 解密文件
     * @throws Exception
     */
    public static File decryptFile(String encryptFilePath) throws Exception {
        //获取私钥
        String privateKey = readFile(KEY_PATH + File.separator + PRIVATE_KEY_FILE_NAME );
        String encryptMsg = readFile( encryptFilePath );
        String targetFilePath = encryptFilePath.substring(0, encryptFilePath.lastIndexOf(".")) + ".gz";//TODO gz.gz
        File decryptFile = new File( targetFilePath );
        if(decryptFile.exists()){
            decryptFile.delete();
        }
        try {
            String decryptMsg = decrypt(encryptMsg, privateKey);
            byte[] buffer = Base64.decodeBase64(decryptMsg);
            FileOutputStream  fileOutputStream = new FileOutputStream( decryptFile );
            BufferedOutputStream out = new BufferedOutputStream( fileOutputStream , DEFAUT_BUFFER_SIZE );
            out.write(buffer);
            out.flush();
            out.close();
        } catch (Exception e) {
            throw new Exception("文件加密失败", e);
        }
        return decryptFile;
    }


    /**加密字符串
     * @param inputData 待加密明文
     * @param publicKey 公钥
     * @return
     * @throws Exception
     */
    public static String encrypt(String inputData, String publicKey) throws Exception {
        Key key = KeyFactory.getInstance(KEY_ALGORITHM)
                .generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey.getBytes(CHARSET))));
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(1, key);
        byte[] resultBytes = doFinalBySegment(inputData.getBytes(CHARSET), cipher, MAX_ENCRYPT_BLOCK);
        return new String(Base64.encodeBase64(resultBytes), CHARSET);
    }

    /**解密字符串
     * @param str 待解密密文
     * @param privateKey 私钥
     * @return
     * @throws Exception
     */
    public static String decrypt(String str, String privateKey) throws Exception {
        byte[] inputBytes = Base64.decodeBase64(str.getBytes(CHARSET));
        byte[] keyBytes = Base64.decodeBase64(privateKey.getBytes(CHARSET));
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM)
                .generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(2, priKey);
        byte[] resultBytes = doFinalBySegment(inputBytes, cipher, MAX_DECRYPT_BLOCK);
        return new String(resultBytes, CHARSET);
    }

    /**分段加解密
     * @param inputBytes
     * @param cipher
     * @param maxLength
     * @return
     * @throws Exception
     */
    private static byte[] doFinalBySegment(byte[] inputBytes, Cipher cipher, int maxLength) throws Exception {
        int inputLenth = inputBytes.length;
        int offSet = 0;
        byte[] resultBytes = new byte[0];
        while (inputLenth - offSet > 0) {
            byte[] cache;

            if (inputLenth - offSet > maxLength) {
                cache = cipher.doFinal(inputBytes, offSet, maxLength);
                offSet += maxLength;
            } else {
                cache = cipher.doFinal(inputBytes, offSet, inputLenth - offSet);
                offSet = inputLenth;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

    /**获取私钥
     * @param keyMap
     * @return
     */
    public static String getPrivateKeyStr(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }

    /**获取公钥
     * @param keyMap
     * @return
     */
    public static String getPublicKeyStr(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }

    /**初始化公钥私钥
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static Map<String, Object> initKey() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();

        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>();

        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**保存生成的公钥私钥
     * @param savePath
     * @throws Exception
     */
    public static void getKeyAndSave(String savePath) throws Exception {
        try {
            Map<String, Object> keyMap = initKey();
            System.out.println("密钥保存路径为：" + savePath);
            writeFile(getPrivateKeyStr(keyMap), savePath + File.separator + PRIVATE_KEY_FILE_NAME );
            writeFile(getPublicKeyStr(keyMap), savePath +  File.separator + PUBLIC_KEY_FILE_NAME );
        } catch (Exception e) {
            throw new Exception("生成密钥失败", e);
        }
    }

    /**读取文件
     * @param filePath
     * @return
     * @throws Exception
     */
    public static String readFile(String filePath) throws Exception {
        StringBuilder data = new StringBuilder();
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(new File(filePath)),
                    StandardCharsets.UTF_8);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader,DEFAUT_BUFFER_SIZE);
            String readLine;
            while ((readLine = bufferedReader.readLine()) != null) {
                data.append(readLine);
            }
            System.out.println("文件读取成功：" + filePath);
            bufferedReader.close();
            return data.toString();
        } catch (Exception e) {
            throw new Exception("文件读取失败", e);
        }
    }

    /**写入文件
     * @param msg
     * @param filePath
     * @return
     * @throws Exception
     */
    public static void writeFile(String msg, String filePath) throws Exception {
        try {
            File file = new File(filePath);
            OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
            out.write(msg);
            out.flush();
            out.close();
        } catch (Exception e) {
            throw new Exception("写入文件失败", e);
        }
    }

    public static void main(String[] arsg) throws Exception {

        //1.生成RSA非对称秘钥对
        getKeyAndSave(KEY_PATH);

        String tarFileName = "C:\\Users\\adtec\\Desktop\\00100220200713.tar.gz";

        //2.加密文件
        long start1 = System.currentTimeMillis();
        File encryptFile = encryptFile(tarFileName);
        long end1 = System.currentTimeMillis();
        System.out.println( "加密文件："+encryptFile.getAbsolutePath() +"===加密耗时：" + (end1 - start1));


        //3.解密文件
        long start2 = System.currentTimeMillis();
        File decryptFile =  decryptFile(encryptFile.getAbsolutePath());
        long end2 = System.currentTimeMillis();
        System.out.println( "解密文件：" + decryptFile.getAbsolutePath() + "===解密耗时：" + (end2 - start2));

    }
}
